#include "stdafx.h"
#include "DebugDraw.h"

#include "Renderer.h"
#include "Light.h"
#include "Mesh.h"
#include "MeshInstance.h"
#include "LoadShader.h"
#include "InputLayouts.h"
#include "BoundingBox.h"

using namespace mgfx;

static ShaderID wireframeShader;
static Mesh * sphereMesh;
static Mesh * coneMesh;
static Mesh * boxMesh;

void mgfx::debug::init()
{
	wireframeShader = loadShaderFromFile("Wireframe.fx");

	MeshDesc desc;
	desc.PositionsOnly = true;
	desc.FileName = "lightgeometry_sphere.3ds";
	sphereMesh = Mesh::createFromFile(desc);

	desc.FileName = "lightgeometry_cone.3ds";
	coneMesh = Mesh::createFromFile(desc);

	desc.FileName = "debug_box_sidelen1.3ds";
	boxMesh = Mesh::createFromFile(desc);
}

void setupRenderer()
{
	Renderer * rdr = Renderer::Instance;
	rdr->setPrimitiveType(TRIANGLE_LIST);
	rdr->setVertexFormat(VertexPosition::VertexFormat);
	TextureID framebuffer = rdr->getDefaultFramebuffer();
	rdr->setRenderTargets(1, &framebuffer, rdr->getDefaultDepthStencilBuffer());
	rdr->setShader(wireframeShader);
}

void mgfx::debug::renderMeshBoundingSphere(const MeshInstance & meshInst, XMMATRIX & viewProj)
{
	setupRenderer();

	Renderer * rdr = Renderer::Instance;
	rdr->setVertexBuffer(sphereMesh->GetParts()[0].VB);
	rdr->setIndexBuffer(sphereMesh->GetParts()[0].IB);

	Mesh & mesh = *meshInst.Mesh_;

	XMMATRIX meshWorldViewProj = XMMatrixMultiply(XMLoadFloat4x4(&meshInst.WorldTransform), viewProj);

	uint partsSize = mesh.GetParts().size();
	for (uint partIdx = 0; partIdx < partsSize; partIdx++)
	{
		const Mesh::Part & part = mesh.GetParts()[partIdx];

		// transform mesh part bounding sphere to world space using mesh world transform
		// and create world transform for mesh part's bounding sphere
		mmath::BoundingSphere bSphereXFormed = mmath::BoundingSphere::transform(part.BSphere,
			XMLoadFloat3(&meshInst.Position), XMLoadFloat4(&meshInst.Orientation), XMLoadFloat3(&meshInst.Scale) );

		float radius = bSphereXFormed.Radius;
		XMMATRIX partWorldViewProj = XMMatrixMultiply(
			XMMatrixMultiply(
				XMMatrixScaling(radius, radius, radius),
				XMMatrixTranslationFromVector( XMLoadFloat3(&bSphereXFormed.Center) )
			),
			viewProj);

		rdr->setShaderMatrix("gWVP", (float*)&partWorldViewProj);
		rdr->setShaderFloatVector("gColor", (float*)&Color::RED);
		rdr->drawIndexed();
	}
}

void mgfx::debug::renderPointLightBoundingSphere(const PointLight & light, XMMATRIX & viewProj)
{
	setupRenderer();

	Renderer * rdr = Renderer::Instance;
	rdr->setVertexBuffer(sphereMesh->GetParts()[0].VB);
	rdr->setIndexBuffer(sphereMesh->GetParts()[0].IB);

	XMVECTOR lightPos = XMLoadFloat3( &light.Position );

	// why not using sphere mesh's bounding sphere like with spotlights below?
	// assuming exactly radius=1 in mesh?

	XMMATRIX worldViewProj = XMMatrixMultiply(
		XMMatrixMultiply(
			XMMatrixScaling(light.Radius, light.Radius, light.Radius),
			XMMatrixTranslationFromVector(lightPos)),
		viewProj);

	rdr->setShaderMatrix("gWVP", (float*)&worldViewProj);
	rdr->setShaderFloatVector("gColor", (float*)&Color::BLUE);
	rdr->drawIndexed();
}

void mgfx::debug::renderSpotLightBoundingSphere(const SpotLight & light, XMMATRIX & viewProj)
{
	setupRenderer();

	Renderer * rdr = Renderer::Instance;
	rdr->setVertexBuffer(sphereMesh->GetParts()[0].VB);
	rdr->setIndexBuffer(sphereMesh->GetParts()[0].IB);

	float coneCosAngle = light.SpotCosAngle;
	float xyConeScale = light.Radius * sqrtf(1.f - coneCosAngle * coneCosAngle) / coneCosAngle;
	float bSphereScale = (std::max)(xyConeScale, light.Radius);

	mmath::BoundingSphere lightBSphereW = mmath::BoundingSphere::transform(coneMesh->GetParts()[1].BSphere,
		XMLoadFloat3(&light.Position), XMLoadFloat4(&light.Orientation), bSphereScale );

	float radius = lightBSphereW.Radius;
	XMMATRIX world = XMMatrixMultiply(
		XMMatrixScaling(radius, radius, radius),
		XMMatrixTranslationFromVector( XMLoadFloat3(&lightBSphereW.Center) )
		);

	XMMATRIX worldViewProj = XMMatrixMultiply(world, viewProj);

	rdr->setShaderMatrix("gWVP", (float*)&worldViewProj);
	rdr->setShaderFloatVector("gColor", (float*)&Color::GREEN);
	rdr->drawIndexed();
}

void mgfx::debug::renderSpotLightBoundingCone(const SpotLight & light, XMMATRIX & viewProj)
{
	setupRenderer();

	Renderer * rdr = Renderer::Instance;
	rdr->setVertexBuffer(coneMesh->GetParts()[1].VB);
	rdr->setIndexBuffer(coneMesh->GetParts()[1].IB);

	float coneCosAngle = light.SpotCosAngle;
	float xyConeScale = light.Radius * sqrtf(1.f - coneCosAngle * coneCosAngle) / coneCosAngle;

	XMMATRIX lightWorldNoScale = XMMatrixMultiply(
		XMMatrixRotationQuaternion(XMLoadFloat4(&light.Orientation)),
		XMMatrixTranslationFromVector(XMLoadFloat3(&light.Position))
		);

	XMMATRIX worldViewProj = XMMatrixMultiply(
		XMMatrixMultiply(
			XMMatrixScaling(xyConeScale, xyConeScale, light.Radius),
			lightWorldNoScale),
		viewProj);

	rdr->setShaderMatrix("gWVP", (float*)&worldViewProj);
	rdr->setShaderFloatVector("gColor", (float*)&Color::RED);

	rdr->drawIndexed();
}

void mgfx::debug::renderBoundingBox( const mmath::BoundingBox & box, XMMATRIX & viewProj )
{
	setupRenderer();

	Renderer * rdr = Renderer::Instance;
	rdr->setVertexBuffer(boxMesh->GetParts()[0].VB);
	rdr->setIndexBuffer(boxMesh->GetParts()[0].IB);

	XMVECTOR minC = XMLoadFloat3(&box.Min);
	XMVECTOR maxC = XMLoadFloat3(&box.Max);

	XMVECTOR center = XMVectorMultiply( XMVectorAdd(minC, maxC), XMVectorReplicate(0.5f) );
	
	XMMATRIX world = XMMatrixMultiply(
		XMMatrixScalingFromVector(maxC - minC),
		XMMatrixTranslationFromVector( center )
	);

	XMMATRIX worldViewProj = XMMatrixMultiply(world, viewProj);

	rdr->setShaderMatrix("gWVP", (float*)&worldViewProj);
	rdr->setShaderFloatVector("gColor", (float*)&Color::GREEN);

	rdr->drawIndexed();
}
